/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.runtime.config.spring.parsers.delegate;

import org.mule.runtime.config.spring.MuleHierarchicalBeanDefinitionParserDelegate;
import org.mule.runtime.config.spring.parsers.AbstractMuleBeanDefinitionParser;
import org.mule.runtime.config.spring.parsers.MuleDefinitionParser;
import org.mule.runtime.config.spring.parsers.MuleDefinitionParserConfiguration;
import org.mule.runtime.config.spring.parsers.PreProcessor;
import org.mule.runtime.config.spring.parsers.assembly.configuration.PropertyConfiguration;
import org.mule.runtime.core.util.StringUtils;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

/**
 * This allows a set of definition parsers to be used, one after another, to process the same element. This lets multiple beans be
 * generated from a single element.
 *
 * <p>
 * Since each bean typically needs a spearate name, this class guarantees that the name and id attributes are reset before each
 * call. Delegates can then modify these on the element without worrying about interfering with other parsers.
 * </p>
 *
 * <p>
 * Typically, subclasses will add additional processing with {@link org.mule.runtime.config.spring.parsers.PreProcessor} and
 * {@link org.mule.runtime.config.spring.parsers.PostProcessor} anonymous classes.
 * </p>
 */
public abstract class AbstractSerialDelegatingDefinitionParser extends AbstractDelegatingDefinitionParser {

  private int index = 0;
  private boolean first;
  private boolean doReset;
  private String originalId;
  private String originalName;
  private Set handledExceptions = new HashSet();

  public AbstractSerialDelegatingDefinitionParser() {
    this(true); // by default, reset name
  }

  /**
   * @param doReset Should the name be reset after called. This is typically true (it protects the parent from changes made by
   *        children) unless this is itself nested.
   */
  public AbstractSerialDelegatingDefinitionParser(boolean doReset) {
    this.doReset = doReset;
  }

  public AbstractBeanDefinition muleParse(Element element, ParserContext parserContext) {
    if (index == 0 || index >= size()) {
      first = true;
      index = 0;
    } else {
      first = false;
    }
    AbstractBeanDefinition bean = null;
    while (null == bean && index < size()) {
      try {
        MuleDefinitionParser parser = getDelegate(index);
        bean = doSingleBean(index++, parser, element, parserContext);
      } catch (RuntimeException e) {
        if (isExceptionHandled(e)) {
          bean = null;
        } else {
          throw e;
        }
      }
    }
    if (null != bean) {
      if (index == size()) {
        bean.removeAttribute(MuleHierarchicalBeanDefinitionParserDelegate.MULE_REPEAT_PARSE);
      } else {
        bean.setAttribute(MuleHierarchicalBeanDefinitionParserDelegate.MULE_REPEAT_PARSE, Boolean.TRUE);
      }
    }
    return bean;
  }

  protected boolean isExceptionHandled(Exception e) {
    return handledExceptions.contains(e.getClass());
  }

  protected AbstractBeanDefinition doSingleBean(int index, MuleDefinitionParser parser, Element element,
                                                ParserContext parserContext) {
    return parser.muleParse(element, parserContext);
  }

  protected MuleDefinitionParserConfiguration addDelegate(MuleDefinitionParser delegate) {
    delegate.registerPreProcessor(new PreProcessor() {

      public void preProcess(PropertyConfiguration config, Element element) {
        if (first) {
          originalId = element.getAttribute(AbstractMuleBeanDefinitionParser.ATTRIBUTE_ID);
          originalName = element.getAttribute(AbstractMuleBeanDefinitionParser.ATTRIBUTE_NAME);
        } else if (doReset) {
          resetNameAndId(element);
        }
      }
    });
    return super.addDelegate(delegate);
  }

  protected void resetNameAndId(Element element) {
    resetAttribute(element, AbstractMuleBeanDefinitionParser.ATTRIBUTE_ID, originalId);
    resetAttribute(element, AbstractMuleBeanDefinitionParser.ATTRIBUTE_NAME, originalName);
  }

  protected void resetAttribute(Element element, String name, String value) {
    if (StringUtils.isEmpty(value)) {
      if (element.hasAttribute(name)) {
        element.removeAttribute(name);
      }
    } else {
      element.setAttribute(name, value);
    }
  }

  protected void addHandledException(Class exception) {
    handledExceptions.add(exception);
  }

  /**
   * A utility class for selecting certain attributes. If the attributes are enabled, the default is set to block others; if
   * specific attributes are disabled the default is set to allow others.
   *
   * @param delegate
   * @param attributes
   * @param enable
   */
  public static void enableAttributes(MuleDefinitionParser delegate, String[] attributes, boolean enable) {
    // if enabling specific attributes, block globally
    delegate.setIgnoredDefault(enable);

    Iterator names = Arrays.asList(attributes).iterator();
    while (names.hasNext()) {
      String name = (String) names.next();
      if (enable) {
        delegate.removeIgnored(name);
      } else {
        delegate.addIgnored(name);
      }
    }
  }

  public static void enableAttributes(MuleDefinitionParser delegate, String[][] attributes) {
    for (int i = 0; i < attributes.length; ++i) {
      enableAttributes(delegate, attributes[i], true);
    }
  }

  public static void enableAttributes(MuleDefinitionParser delegate, String[] attributes) {
    enableAttributes(delegate, attributes, true);
  }

  public static void enableAttribute(MuleDefinitionParser delegate, String attribute) {
    enableAttributes(delegate, new String[] {attribute}, true);
  }

  public static void disableAttributes(MuleDefinitionParser delegate, String[][] attributes) {
    for (int i = 0; i < attributes.length; ++i) {
      enableAttributes(delegate, attributes[i], false);
    }
  }

  public static void disableAttributes(MuleDefinitionParser delegate, String[] attributes) {
    enableAttributes(delegate, attributes, false);
  }

  public static void disableAttribute(MuleDefinitionParser delegate, String attribute) {
    enableAttributes(delegate, new String[] {attribute}, false);
  }

}
